﻿//是否写日志
#define txtLogAll
#undef DEBUG
namespace tjb.Utility.Config
{
    using System;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Runtime.Serialization.Formatters.Soap;
    using System.Threading;
    using System.Web;
    using System.Web.Caching;
    using System.Xml.Serialization;
    using tjb.Utility.IConfig;
    using System.Diagnostics;

    /// <summary>
    /// 配置文件读取或保存时发生错误时的处理方法
    /// </summary>
    public delegate void SystemConfigErrorHandler<T>(T source, Exception e) where T : SystemConfigBase<T>, new();
    /// <summary>
    /// 配置文件发生改变而引起值改变
    /// </summary>
    public delegate void Changed<T>(FileSystemWatcher watcher, T value) where T : SystemConfigBase<T>, new();
    /// <summary>
    /// 序列化基类  by Tjb 2007/06 
    /// T 类型不能创建多态的多个实例
    /// <para>
    /// 优化和修正，增加配置文件备份 2008/9/18
    /// </para>
    /// <para>
    /// 修改：获取实例时加锁
    /// </para>
    /// <para>
    /// 修改： by tjb @ 2008/10/24
    /// SaveAs(T Tobj, string FileFullName, bool BackupOnError) 方法增加线程锁定。防止多线程冲突
    /// </para>
    /// <para>
    /// 修改： by tjb @ 2008/12/16
    /// 优化线程锁，以及优化缓存key的获取方式，使得typeof(T)运算只运行一次
    /// </para>
    /// </summary>
    /// <typeparam name="T">T 必须有无参数的构造函数</typeparam>
    [Serializable]
    public abstract class SystemConfigBase<T> where T : SystemConfigBase<T>, new()
    {
        private static T tCache;
        private Formatter<T> formatter;

        /// <summary>
        /// 构造函数
        /// </summary>
        public SystemConfigBase()
        {
            formatter = new Formatter<T>(this.FormatterType);
            Init();

        }
        /// <summary>
        /// 
        /// </summary>
        virtual protected void Init()
        {

        }

        #region 文件监视

        //protected FileSystemWatcher watcher;
        //private void InitWatcher()
        //{
        //    using (Lock l = new Lock(_Locker))
        //    {

        //        if (!string.IsNullOrEmpty(this.ConfigFileFullName) && File.Exists(this.ConfigFileFullName))
        //        {
        //            string dir = Path.GetDirectoryName(this.ConfigFileFullName);
        //            if (watcher == null || watcher.Path != dir)
        //            {
        //                if (watcher != null) watcher.Dispose();
        //                if (!string.IsNullOrEmpty(dir))
        //                {
        //                    watcher = new FileSystemWatcher(dir);
        //                    watcher.NotifyFilter = NotifyFilters.LastWrite;
        //                    watcher.IncludeSubdirectories = true;
        //                    watcher.Changed += new FileSystemEventHandler(watcher_Changed);
        //                    watcher.EnableRaisingEvents = true;
        //                }
        //            }
        //        }
        //    }
        //}



        //void watcher_Changed(object sender, FileSystemEventArgs e)
        //{

        //    if (e.ChangeType == WatcherChangeTypes.Changed)
        //    {
        //        tCache = Read();
        //    }
        //    else if (e.ChangeType == WatcherChangeTypes.Created)
        //    {
        //        tCache = Read();
        //    }
        //    else if (e.ChangeType == WatcherChangeTypes.Deleted)
        //    {
        //        tCache = null;
        //    }
        //    else if (e.ChangeType == WatcherChangeTypes.Renamed)
        //    {
        //        tCache = null;
        //    }
        //    if (OnChanged != null)
        //    {
        //        OnChanged(watcher, tCache);
        //    }
        //}
        ///// <summary>
        ///// 配置文件发生改变而引起配置变化
        ///// <para>by tangjingbo at 2009-9-25 16:21</para>
        ///// </summary>
        //public event Changed<T> OnChanged;
        ///// <summary>
        ///// 设置是否监视配置文件
        ///// <para>by tangjingbo at 2009-9-25 16:20</para>
        ///// </summary>
        ///// <param name="enable">if set to <c>true</c> [enable].</param>
        //protected void SetWatcherEnable(bool enable)
        //{
        //    if (watcher != null)
        //    {
        //        watcher.EnableRaisingEvents = enable;
        //    }
        //}
        #endregion
        #region Exceptions
        [NonSerialized]
        private Exception __ConfigException;
        /// <summary>
        /// 读取或保存时发生的最后一个错误
        /// </summary>
        [XmlIgnore, SoapIgnore]
        public Exception LastException
        {
            get
            {
                return __ConfigException;
            }
            private set
            {
                if (value != null)
                {
                    __ConfigException = value;
                    Error(tCache, __ConfigException);
                }
            }
        }
        virtual protected void Error(T source, Exception e)
        {
            if (OnError != null)
                OnError(source, e);
        }
        /// <summary>
        /// 发生错误时
        /// <para>by tangjingbo at 2009-9-3 11:49</para>
        /// </summary>
        public event SystemConfigErrorHandler<T> OnError;

        #endregion

        #region 序列化类型  可重写
        private FormatType _formatterType = FormatType.Xml;
        /// <summary>
        /// 序列化的方式
        /// </summary>      
        [XmlIgnore]
        protected virtual FormatType FormatterType
        {
            get
            {
                return _formatterType;
            }
            set
            {
                _formatterType = value;
            }
        }

        #endregion

        #region 获取单个实例


        [NonSerialized]
        private static Type _Locker = typeof(T);
        /// <summary>
        /// 从缓存（缓存KEY为T的命空间加类名）获取当前类的实例，如果缓存不存在则读取文件(ConfigFileFullName)， 并缓存。 
        /// </summary>
        public static T Instance
        {
            get
            {
                if (tCache == null || tCache.isReRead)
                {
                    //这中方式无异于不加锁
                    //object Monitor = new object();
                    //lock (Monitor)
                    using (Lock l = new Lock(_Locker, 5000))
                    {
                        if (tCache == null || tCache.isReRead)
                        {
                            tCache = new T().Read();
                        }
                    }
                }
                return tCache;
            }
        }
        private bool isReRead = true;

        /// <summary>
        /// 是否重新读取
        /// <para>by tangjingbo at 2009-7-17 16:40</para>
        /// </summary>
        /// <value></value>
        [XmlIgnore, SoapIgnore]
        public bool IsReRead
        {
            get { return isReRead; }
        }

        #endregion

        #region Caching

        /// <summary>
        /// 从ConfigFileName指定文件以CurrentSerializeType读取实例，如果存在缓存则移除并重新进行缓存
        /// 如果不存在则以InitConfig初始化类,创建并保存文件
        /// </summary>
        /// <returns></returns>
        protected T ResetCacheAndRead()
        {
            ResetCache();
            return Read();
        }
        /// <summary>
        /// 移除CacheKEY 缓存
        /// </summary>
        public void ResetCache()
        {
            isReRead = true;
        }
        #endregion

        #region 获取
        /// <summary>
        /// 读取缓存中的实例如果不存在则
        /// 从ConfigFileName指定文件以CurrentSerializeType读取实例
        /// 如果不存在则以InitConfig初始化类,创建并保存文件
        /// </summary>
        /// <returns></returns>
        virtual protected T Read()
        {
            return Read(ConfigFileFullName, true, FormatterType);
        }

        /// <summary>
        ///  从ConfigFileName指定文件以CurrentSerializeType读取实例
        ///  如果不存在则以InitConfig初始化类,创建并保存文件
        /// </summary>
        /// <param name="configFileFullName">缓存文件绝对路径</param>
        /// <param name="serializeType"></param>
        /// <param name="BackupOnError">读取错误时是否备份</param>
        /// <returns></returns>
        protected T Read(string configFileFullName, bool BackupOnError, FormatType serializeType)
        {

            T t = tCache;
            if (t != null)
                return t;

            using (Lock l = new Lock(_Locker, 10000))
            {
                FileInfo fi = new FileInfo(configFileFullName);
                if (fi.Exists)
                {

                    FileStream fs = null;
                    try
                    {
                        #region  open the stream...
                        // open the stream...
                        fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                        t = formatter.Deserialize(fs);
                        #endregion
                        if (fs != null)
                            fs.Close();

                    }
                    catch (Exception ex)
                    {
                        if (fs != null)
                            fs.Close();
                        #region 读取失败 进行备份 防止以后使用默认配置覆盖当前数据
                        if (BackupOnError)
                            if (fi.Exists)
                            {
                                string BackupFilePath = fi.FullName + ".{Read" + DateTime.Now.ToString("yyMMddHHssfff") + "}.bak";
                                try
                                {
                                    fi.CopyTo(BackupFilePath, true);
                                }
                                catch (Exception ioex)
                                {
                                    t.LastException = new Exception("读取配置文件出错后，对配置文件进行备份发生错误！", ioex);
                                }
                            }
                        #endregion
                        t = CustomizingInit();
                        t.LastException = new Exception("读取配置文件时发生错误！将返回CustomizingInit()方法的实例，默认为 new T()！", ex);
                    }
                }
                else
                {
                    #region 配置文件不存在

                    t = CustomizingInit();

                    //不存在的同一文件不能读多次
                    //防止在CustomizingInit()方法中调用Read(string configFileName, SerializeType serializeType) 造成死循环
                    string ProtectedKEY = "Config_InitProtectedKEY" + Thread.CurrentThread.ManagedThreadId + fi.FullName;
                    if (CacheUtility.GetAnyType<bool>(ProtectedKEY) == false)
                    {
                        CacheUtility.SetAbsoluteExpiration(ProtectedKEY, true, null, 0.02);
                    }
                    else
                    {
                        CacheUtility.Remove(ProtectedKEY);
                        LastException = new Exception("'" + fi.FullName + "' 文件不存在！读取过于频繁。导致此保护异常发生。");
                    }
                    t.LastException = new Exception("配置文件：'" + fi.FullName + "' 不存在！默认返回CustomizingInit()方法返回的实例。");
                    #endregion
                }
                if (t == null)
                    t.LastException = new Exception("发生错误，请检查初始化配置方法 CustomizingInit() 未返回的配置实例不能为空");
            }
            t.isReRead = false;
            return t;
        }
        #endregion

        #region 保存
        /// <summary>
        /// 将当前实例进行保存
        /// </summary>
        /// <param name="BackupOnError">发生错误时是否备份</param>
        /// <returns></returns>
        public bool Save(bool backupOnError)
        {
            if (Current == null)
            {
                LastException = new Exception(_Locker.FullName + " 未正确实现 Current；");
                throw LastException;
            }
            return Save(Current, backupOnError);
        }
        /// <summary>
        /// 将当前实例进行保存
        /// 发生错误时将备份配置文件
        /// </summary>
        /// <returns></returns>
        public virtual bool Save()
        {
            return Save(true);
        }

        /// <summary>
        /// 将当前实例保存到T.ConfigFileName指定的配置文件
        /// </summary>
        /// <param name="Tobj"></param>
        /// <param name="BackupOnError"></param>
        /// <returns></returns>      
        public bool Save(T Tobj, bool BackupOnError)
        {
            return SaveAs(Tobj, ConfigFileFullName, BackupOnError);
        }

        /// <summary>
        /// 将一个实例对象保存到FileFullName指定的配置文件
        /// </summary>
        /// <param name="FileFullName"></param>
        /// <param name="Tobj"></param>
        /// <param name="BackupOnError"></param>
        /// <returns></returns>      
        protected bool SaveAs(T Tobj, string FileFullName, bool BackupOnError)
        {
            bool result = false;
            using (Lock l = new Lock(_Locker, 10000))
            {

                FileStream fs = null;
                FileInfo fi = new FileInfo(FileFullName);
                string BackupFilePath = null;

                if (BackupOnError)
                {
                    #region backup
                    try
                    {
                        //如果存在先备份
                        if (fi.Exists)
                        {
                            BackupFilePath = fi.FullName + ".{Save" + DateTime.Now.ToString("yyMMddHHssfff}")/* + Guid.NewGuid().ToString("B") */+ ".bak";
                            fi.CopyTo(BackupFilePath, true);
                        }
                        else if (!fi.Directory.Exists)
                        {
                            //目录不存在创建目录
                            fi.Directory.Create();
                            //创建目录并监视目录

                        }
                    }
                    catch (Exception ex)
                    {
                        Tobj.LastException = new Exception("备份配置文件时发生错误！", ex);
                        result = false;
                    }
                    #endregion
                }

                try
                {
                    if (fi.Exists)
                        fi.Delete();
                    #region  serialize it...
                    fs = new FileStream(fi.FullName, FileMode.Create, FileAccess.Write, FileShare.Read);

                    formatter.Serialize(fs, Tobj);
                    #endregion

                    #region 成功后进行缓存
                    tCache = Tobj;
                    #endregion

                    #region 成功则删除当前备份
                    if (BackupOnError && !string.IsNullOrEmpty(BackupFilePath))
                        try
                        {
                            File.Delete(BackupFilePath);
                        }
                        catch (Exception ex)
                        {
                            Tobj.LastException = new Exception("删除备份文件时发生错误！", ex);
                        }
                    #endregion

                    result = true;
                }
                catch (UnauthorizedAccessException ioex)
                {
                    Tobj.LastException = new Exception(string.Format("保存配置文件时发生IO错误！[TheadID:{0},ThreadName:{1}]", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Name), ioex);
                    result = false;
                }
                catch (Exception ex)
                {
                    Tobj.LastException = new Exception(string.Format("保存配置文件时发生错误！[TheadID:{0},ThreadName:{1}]", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Name), ex);
                    result = false;
                }
                finally
                {
                    if (fs != null)
                        fs.Close();

                }
            }
            return result;
        }
        #endregion

        #region 删除配置文件
        /// <summary>
        /// 删除配置文件
        /// </summary>
        /// <returns></returns>
        public bool Delete()
        {
            return Delete(ConfigFileFullName);
        }
        /// <summary>
        /// 删除指定的配置文件 .文件不存在不引发异常。并返回true
        /// </summary>
        /// <param name="FileFullName"></param>
        /// <returns></returns>
        public bool Delete(string FileFullName)
        {
            tCache.isReRead = true;
            try
            {
                if (File.Exists(FileFullName))
                    File.Delete(FileFullName);
                return true;
            }
            catch (Exception ex)
            {
                LastException = ex;
                return false;
            }

        }
        #endregion

        #region  抽象成员

        /// <summary>
        /// 返回派生类的当前实例
        /// <para>protected override T Current</para>
        /// <para>{</para>
        /// <para>  get</para>
        /// <para>  {</para>
        /// <para>      return this;</para>
        /// <para>  }</para>
        /// <para>}</para>        
        /// <para>
        /// by tangjingbo at 2009-2-24 12:21
        /// </para>
        /// </summary>
        /// <value>T</value>
        protected abstract T Current
        {
            get;
        }

        /// <summary>
        /// 自定义初始化配置类
        /// 获取 T 的配置实例时，如果配置文件不存在或者读取失败时返回此实例
        /// 重写中一般读取备份的配置文件，必须保证备份配置文件存在，否则发生异常。
        /// 默认返回    new T();
        /// <para>
        /// by tangjingbo at 2009-2-24 12:22
        /// </para>
        /// </summary>
        /// <returns></returns>
        protected virtual T CustomizingInit()
        {
            return Current;
        }
        /// <summary>
        /// 配置文件名
        /// <c>配置文件名</c>
        /// <example>
        /// 配置文件名
        /// </example>
        /// </summary>
        // [NonSerialized]

        private string ___file = Path.Combine(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase, @"App_Data\" + _Locker.Name + ".config");


        /// <summary>
        /// 配置文件全路径名
        /// 默认：winform 在应用程序目录；web应用程序默认bin目录。
        /// </summary>
        [XmlIgnore, SoapIgnore]
        public virtual string ConfigFileFullName
        {
            get
            {
                return ___file;
            }
            set
            {
                ___file = value;

            }
        }
        #endregion
    }
}
